package com.ideaaedi.zoo.example.springboot.auth.satoken.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import com.alibaba.fastjson2.JSON;
import com.ideaaedi.commonds.exception.ExceptionUtil;
import com.ideaaedi.zoo.commonbase.entity.codemsg.CodeMsgProvider;
import com.ideaaedi.zoo.commonbase.exception.ZooException;
import com.ideaaedi.zoo.diy.artifact.auth.satoken.properties.ZooAuthProperties;
import com.ideaaedi.zoo.diy.artifact.auth.satoken.properties.nested.ZooAuthBasic;
import com.ideaaedi.zoo.example.springboot.auth.satoken.entity.Result;
import com.ideaaedi.zoo.example.springboot.auth.satoken.enums.DemoCodeMsgEnum;
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * spring-mvc 异常处理器
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
@Slf4j
@RestControllerAdvice
public class DemoSaTokenExceptionHandler implements SaFilterErrorStrategy {
    @Resource
    private ZooAuthProperties zooAuthProperties;
    
    // ------------------------------------------------- sa-token 异常处理 -------------------------------------------------
    
    @Override
    public Object run(Throwable e) {
        SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
        if (e instanceof SaTokenException) {
            return JSON.toJSONString(handleSaTokenException((SaTokenException) e));
        }
        Result<Object> result = Result.failure(DemoCodeMsgEnum.FILTER_AUTH_OCCUR_EXCEPTION);
        String message = e.getMessage();
        message = message != null ? message : e.toString();
        result.setData(message);
        log.error("SaFilterErrorStrategy occur exception message -> {}, Result -> {}", message, result, e);
        return JSON.toJSONString(result);
    }
    
    /**
     * 处理认证鉴权相关异常
     */
    @ExceptionHandler(SaTokenException.class)
    public Result<Object> handleSaTokenException(SaTokenException e) {
        ZooAuthBasic basic = zooAuthProperties.getBasic();
        Map<Integer, CodeMsgProvider> errorMapping = basic == null ? null : basic.getErrorMapping();
        int code = e.getCode();
        Result<Object> result = null;
        if (errorMapping != null) {
            CodeMsgProvider codeMsgProvider = errorMapping.get(code);
            if (codeMsgProvider != null) {
                result = Result.failure(codeMsgProvider);
            }
        }
        result = result == null ? Result.failure(DemoCodeMsgEnum.AUTH_OCCUR_EXCEPTION) : result;
        String message = e.getMessage();
        message = message != null ? message : e.toString();
        result.setData(code + "." + message);
        log.error("DemoSaTokenExceptionHandler#handleSaTokenException message -> {}, Result -> {}",
                message, result, e);
        return result;
    }
    
    // ------------------------------------------------- 其它异常处理 -------------------------------------------------
    
    
    /**
     * 处理 基类业务异常
     */
    @ExceptionHandler(ZooException.class)
    public Result<Object> handleBaseException(ZooException e) {
        Object[] placeholders = e.getPlaceholders();
        CodeMsgProvider codeMsgProvider = e.getCodeMsgProvider();
        Result<Object> result = null;
        if (codeMsgProvider instanceof Result) {
            //noinspection unchecked,rawtypes
            result = (Result)codeMsgProvider;
        }
        result = Result.failure(result == null ? e.getData() : result.getData(), codeMsgProvider, placeholders);
        log.error("DefaultExceptionHandler#handleBaseException  e.getMessage() -> {}, Result -> {}", e.getMessage(), result, e);
        return result;
    }
    
    /**
     * 处理IllegalArgumentException异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public Result<Object> handleIllegalArgumentException(IllegalArgumentException e) {
        Result<Object> result = Result.failure(DemoCodeMsgEnum.ARGS_ILLEGAL);
        String errorMessage = e.getMessage();
        result.setMsg(DemoCodeMsgEnum.ARGS_ILLEGAL.getMessage() + ". " + errorMessage);
        log.error("DefaultExceptionHandler#handleIllegalArgumentException e.getMessage() -> {}, Result -> {}",
                e.getMessage(), result, e);
        return result;
    }
    
    /**
     * JSR303校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> handleMethodargumentnotvalidexception(MethodArgumentNotValidException e) {
        Result<Object> result = Result.failure(DemoCodeMsgEnum.ARGS_ILLEGAL);
        String errorMessage;
        try {
            List<ObjectError> errorList = e.getBindingResult().getAllErrors();
            errorMessage = errorList.get(0).getDefaultMessage();
            result.setMsg(DemoCodeMsgEnum.ARGS_ILLEGAL.getMessage() + ". " + errorMessage);
        } catch (Exception ex) {
            log.warn("DefaultExceptionHandler#handleMethodargumentnotvalidexception occur error while parsing error zoo_info. ex.getMessage() -> {}", ex.getMessage());
        }
        log.error("DefaultExceptionHandler#handleMethodargumentnotvalidexception e.getMessage() -> {}, result -> {}",
                e.getMessage(), result, e);
        return result;
    }
    
    /**
     * JSR303校验异常
     */
    @ExceptionHandler(ValidationException.class)
    public Result<Object> handleValidationException(ValidationException e) {
        Result<Object> result = Result.failure(DemoCodeMsgEnum.ARGS_ILLEGAL);
        String errorMessage;
        try {
            StringBuilder sb = new StringBuilder();
            if (e instanceof ConstraintViolationException exs) {
                Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
                for (ConstraintViolation<?> item : violations) {
                    sb.append(item.getMessage()).append(",");
                }
            }
            errorMessage = sb.toString();
            result.setMsg(DemoCodeMsgEnum.ARGS_ILLEGAL.getMessage() + ". " + errorMessage);
        } catch (Exception exception) {
            log.warn("DefaultExceptionHandler#handleValidationException occur error while parsing error zoo_info. exception.getMessage() -> {}",
                    exception.getMessage());
        }
        log.error("DefaultExceptionHandler#handleValidationException e.getMessage() -> {}, result -> {}", e.getMessage(), result, e);
        return result;
    }
    
    /**
     * JSR303校验异常
     */
    @ExceptionHandler(BindException.class)
    public Result<Object> handleBindException(BindException e) {
        Result<Object> result = Result.failure(DemoCodeMsgEnum.ARGS_ILLEGAL);
        String errorMessage;
        try {
            StringBuffer sb = new StringBuffer();
            e.getBindingResult().getAllErrors().forEach((error) -> {
                String fieldName = ((FieldError) error).getField();
                String tmpErrorMessage = error.getDefaultMessage();
                sb.append(fieldName).append("：").append(tmpErrorMessage).append(",");
            });
            errorMessage = sb.toString();
            result.setMsg(DemoCodeMsgEnum.ARGS_ILLEGAL.getMessage() + ". " + errorMessage);
        } catch (Exception ex) {
            log.warn("DefaultExceptionHandler#handleBindException occur error while parsing error zoo_info. ex.getMessage() -> {}", ex.getMessage());
        }
        log.error("DefaultExceptionHandler#handleBindException e.getMessage() -> {}, result -> {}", e.getMessage(), result, e);
        return result;
    }
    
    /**
     * 处理 读取http消息错误
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result<Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        Result<Object> result = Result.failure(e.getMessage(), DemoCodeMsgEnum.HTTP_MSG_READ_ERROR);
        log.error("DefaultExceptionHandler#handleHttpMessageNotReadableException  e.getMessage() -> {}, Result -> {}", e.getMessage(), result, e);
        return result;
    }
    
    /**
     * 处理 http方法类型不支持异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        Result<Object> result = Result.failure(e.getMessage(), DemoCodeMsgEnum.HTTP_REQUEST_METHOD_NOT_SUPPORTED_FAIL);
        log.error("DefaultExceptionHandler#handleHttpRequestMethodNotSupportedException  e.getMessage() -> {}, Result -> {}", e.getMessage(), result, e);
        return result;
    }
    
    /**
     * 处理 缺少RequestParameter参数
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result<Object> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        Result<Object> result = Result.failure(e.getMessage(), DemoCodeMsgEnum.MISSING_SERVLET_REQUEST_PARAMETER_FAIL);
        log.error("DefaultExceptionHandler#handleMissingServletRequestParameterException  e.getMessage() -> {}, Result -> {}", e.getMessage(), result, e);
        return result;
    }
    
    /**
     * 处理 检查异常
     */
    @ExceptionHandler(Exception.class)
    public Result<Object> handleException(Exception e) {
        ZooException zooException = ExceptionUtil.extractThrowable(e, ZooException.class, 5);
        if (zooException != null) {
            log.error("redirect to handleBaseException. raw e.getMessage() -> {}", e.getMessage());
            return handleBaseException(zooException);
        }
        Result<Object> result = Result.failure();
        String message = e.getMessage();
        message = message != null ? message : e.toString();
        log.error("DefaultExceptionHandler#handleException message -> {}, result -> {}", message, result, e);
        return result;
    }
}
